using System;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using DanielSig.Geometry;

namespace DanielSig.UnityExtensions
{
	public class Edge
	{
		public Vector3 LeftNormal;
		public Vector3 RightNormal;
		
		public Vector3 Start;
		public Vector3 End;
	}
	public static class XMesh3
	{
		#region FOR EACH EDGE
		private class TempEdge
		{
			public Vector3 LeftNormal;
			public Vector3 RightNormal;
			
			public int OtherIndex;
		}
		public static void ForEachEdge(this Mesh m, Action<Edge> func)
		{
			LinkedList<TempEdge>[] edges = new LinkedList<TempEdge>[m.vertexCount];
			Vector3[] verts = m.vertices;
			int[] triangles = m.triangles;
			int length = triangles.Length;
			for(int i = 0; i < length; i+=3)
			{
				int i0 = triangles[i];
				int i1 = triangles[i+1];
				int i2 = triangles[i+2];
				Vector3 normal = Vector3.Cross(verts[i1] - verts[i0], verts[i2] - verts[i0]).normalized;
				CheckEdge(edges, i0, i1, ref normal);
				CheckEdge(edges, i1, i2, ref normal);
				CheckEdge(edges, i2, i0, ref normal);
			}
			length = edges.Length;
			for(int i = 0; i < length; i++)
			{
				LinkedList<TempEdge> tempEdges = edges[i];
				if(tempEdges != null)
				{
					foreach(TempEdge tempEdge in tempEdges)
					{
						Edge e = new Edge
						{
							Start = verts[i],
							End = verts[tempEdge.OtherIndex],
							LeftNormal = tempEdge.LeftNormal,
							RightNormal = tempEdge.RightNormal
						};
						func(e);
					}
				}
			}
		}
		private static void CheckEdge(LinkedList<TempEdge>[] edges, int firstIndex, int secondIndex, ref Vector3 normal)
		{
			if(firstIndex > secondIndex)
			{
				//swap
				int temp = firstIndex;
				firstIndex = secondIndex;
				secondIndex = temp;
			}
				
			LinkedList<TempEdge> tempEdges = edges[firstIndex];
			if(tempEdges == null)
			{
				edges[firstIndex] = tempEdges = new LinkedList<TempEdge>();
			}
			
			TempEdge tempEdge = tempEdges.FirstOrDefault(x => x.OtherIndex == secondIndex);
			if(tempEdge == null)
			{
				tempEdges.AddLast(new TempEdge{ OtherIndex = secondIndex, LeftNormal = normal});
			}
			else
			{
				tempEdge.RightNormal = normal;
			}
		}
		#endregion
		
		
		#region SHARP EDGES
		public static SharpEdge[] GetSharpEdges(this Mesh mesh, float threshold, int maxEdges, float edgeThickness, float optimization)
		{
			LinkedList<SharpEdge> sharpEdges = new LinkedList<SharpEdge>();
			float invThreshold = 1F / threshold;
			threshold = 1F - threshold;
			mesh.ForEachEdge
			(
				x =>
				{
					float sharpness = (1F-x.LeftNormal.Dot(x.RightNormal)) / 2;
					if(maxEdges > 0 && sharpness > threshold)
					{
						maxEdges--;
						sharpEdges.AddLast(new SharpEdge(x.Start, x.End, (x.RightNormal - x.LeftNormal).normalized, edgeThickness, (sharpness - threshold) * invThreshold));
					}
				}
			);
			
			OptimizeSharpEdges(sharpEdges, optimization);
			OptimizeSharpEdges(sharpEdges, optimization);
			return sharpEdges.ToArray();
		}
		
		private static void OptimizeSharpEdges(LinkedList<SharpEdge> sharpEdges, float optimization)
		{
			optimization = 1F - optimization;
			
			LinkedListNode<SharpEdge> edge1Node = sharpEdges.First;
			while(edge1Node != null)
			{
				SharpEdge edge1 = edge1Node.Value;
				LinkedListNode<SharpEdge> edge2Node = edge1Node.Next;
				while(edge2Node != null)
				{
					SharpEdge edge2 = edge2Node.Value;
					LinkedListNode<SharpEdge> next = edge2Node.Next;
					
					if(edge1 != edge2)
					{
						Vector3 a1 = edge1.Line.Start;
						Vector3 a2 = edge1.Line.End;
						Vector3 b1 = edge2.Line.Start;
						Vector3 b2 = edge2.Line.End;
						if(a1 == b1 || a1 == b2 || a2 == b1 || a2 == b2)
						{
							if(edge1.Line.Normal.Dot(edge2.Line.Normal).Abs() > optimization)
							{
								edge1Node.Value = edge1 = edge1 + edge2;
								sharpEdges.Remove(edge2Node);
							}
						}
					}
					edge2Node = next;
				}
				edge1Node = edge1Node.Next;
			}
		}
		#endregion
	}
}

